package com.zhouj.endless.geohash;

import java.io.Serializable;

/**
 * 地理围栏
 */
public class BoundingBox implements Serializable {
    private static final long serialVersionUID = -7145192134410261076L;
    /**
     * 最小纬度(南纬)
     */
    private double southLatitude;
    /**
     * 最大纬度(北纬)
     */
    private double northLatitude;

    /**
     * 最小经度(西经)
     */
    private double westLongitude;
    /**
     * 最大经度(东经)
     */
    private double eastLongitude;
    /**
     * 交点180子午线
     */
    private boolean intersects180Meridian;


    /**
     * 创建由两个坐标定义的边界框
     */
    public BoundingBox(WGS84Point southWestCorner, WGS84Point northEastCorner) {
        this(southWestCorner.getLatitude(), northEastCorner.getLatitude(), southWestCorner.getLongitude(), northEastCorner.getLongitude());
    }

    /**
     * 使用指定的纬度和经度创建边界框。此构造函数考虑点的顺序.
     *
     * @param northLatitude 最大纬度
     * @param southLatitude 最小纬度
     * @param westLongitude 最小经度
     * @param eastLongitude 最大经度
     * @throws IllegalArgumentException 当定义的BoundingBox将越过其中一个极点时。不支持这种盒子。
     */
    //                          最小纬度(南纬)         最大纬度(北纬)     最小经度(西经)         最大经度(东经)
    public BoundingBox(double southLatitude, double northLatitude, double westLongitude, double eastLongitude) {
        if (southLatitude > northLatitude) {
            throw new IllegalArgumentException("南纬不能大于北纬");
        }

        if (Math.abs(southLatitude) > 90 || Math.abs(northLatitude) > 90 || Math.abs(westLongitude) > 180 || Math.abs(eastLongitude) > 180) {
            throw new IllegalArgumentException("提供的坐标超出范围");
        }

        this.northLatitude = northLatitude;
        this.westLongitude = westLongitude;

        this.southLatitude = southLatitude;
        this.eastLongitude = eastLongitude;
        //                      最大经度(东经)<最小经度(西经)
        intersects180Meridian = eastLongitude < westLongitude;
    }

    /**
     * 克隆构造函数
     *
     * @param that
     */
    public BoundingBox(BoundingBox that) {
        this(that.southLatitude, that.northLatitude, that.westLongitude, that.eastLongitude);
    }

    /**
     * 返回此BoundingBox的NorthWestCorner作为新点
     *
     * @return 西北角
     */
    public WGS84Point getNorthWestCorner() {
        return new WGS84Point(northLatitude, westLongitude);
    }

    /**
     * 将此BoundingBox的NorthEastCorner作为新点返回
     *
     * @return 东北角
     */
    public WGS84Point getNorthEastCorner() {
        return new WGS84Point(northLatitude, eastLongitude);
    }

    /**
     * 将此BoundingBox的NorthEastCorner作为新点返回
     *
     * @return 东南角
     */
    public WGS84Point getSouthEastCorner() {
        return new WGS84Point(southLatitude, eastLongitude);
    }

    /**
     * 将此BoundingBox的NorthEastCorner作为新点返回
     *
     * @return 西南角
     */
    public WGS84Point getSouthWestCorner() {
        return new WGS84Point(southLatitude, westLongitude);
    }

    /**
     * 返回边界框的大小（以纬度为单位）。返回的值将始终为正值。
     *
     * @return 维度大小
     */
    public double getLatitudeSize() {
        return northLatitude - southLatitude;
    }

    /**
     * 返回边界框的大小（以经度为单位）。返回的值将始终为正值。
     *
     * @return 经度大小
     */
    public double getLongitudeSize() {
        if (eastLongitude == 180.0 && westLongitude == -180.0) {
            return 360.0;
        }
        double size = (eastLongitude - westLongitude) % 360;

        // Remainder fix for earlier java versions
        if (size < 0) {
            size += 360.0;
        }
        return size;
    }

    /**
     * 判断一个点是否在地理围栏内
     *
     * @param point WGS84Point
     * @return boolean
     */
    public boolean contains(WGS84Point point) {
        return containsLatitude(point.getLatitude()) && containsLongitude(point.getLongitude());
    }

    /**
     * 判断两个地理围栏是否相交
     *
     * @param other BoundingBox
     * @return boolean
     */
    public boolean intersects(BoundingBox other) {
        // 首先检查纬度，因为所有情况下都一样
        if (other.southLatitude > northLatitude || other.northLatitude < southLatitude) {
            return false;
        } else {
            if (!intersects180Meridian && !other.intersects180Meridian) {
                return !(other.eastLongitude < westLongitude || other.westLongitude > eastLongitude);
            } else if (intersects180Meridian && !other.intersects180Meridian) {
                return !(eastLongitude < other.westLongitude && westLongitude > other.eastLongitude);
            } else if (!intersects180Meridian && other.intersects180Meridian) {
                return !(westLongitude > other.eastLongitude && eastLongitude < other.westLongitude);
            } else {
                return true;
            }
        }
    }

    /**
     * 获取地理围栏中心点
     *
     * @return WGS84Point
     */
    public WGS84Point getCenter() {
        double centerLatitude = (southLatitude + northLatitude) / 2;
        double centerLongitude = (westLongitude + eastLongitude) / 2;

        // This can happen if the bbox crosses the 180-meridian
        if (centerLongitude > 180) {
            centerLongitude -= 360;
        }

        return new WGS84Point(centerLatitude, centerLongitude);
    }

    /**
     * 展开此边界框以包括提供的边界框。在距离最小的方向上进行扩展。
     * 如果两个距离相同，它将向东扩展。它不会穿过两极，但会穿过180度子午线，如果这是最短的距离。
     * 如果需要精确指定东北点和西南点，请创建一个新的边界框，您可以在其中单独指定点。
     *
     * @param other
     */
    public void expandToInclude(BoundingBox other) {

        // 扩展纬度
        if (other.southLatitude < southLatitude) {
            southLatitude = other.southLatitude;
        }
        if (other.northLatitude > northLatitude) {
            northLatitude = other.northLatitude;
        }

        // 展开经度
        // 首先，检查两个盒子是否相互包含
        boolean thisContainsOther = containsLongitude(other.eastLongitude) && containsLongitude(other.westLongitude);
        boolean otherContainsThis = other.containsLongitude(eastLongitude) && other.containsLongitude(westLongitude);

        // 新盒子需要遍布全球
        if (thisContainsOther && otherContainsThis) {
            eastLongitude = 180.0;
            westLongitude = -180.0;
            intersects180Meridian = false;
            return;
        }
        // 在本例中已完成
        if (thisContainsOther) {
            return;
        }
        // 展开以匹配更大的框
        if (otherContainsThis) {
            eastLongitude = other.eastLongitude;
            westLongitude = other.westLongitude;
            intersects180Meridian = eastLongitude < westLongitude;
            return;
        }

        // 如果不是这样，则计算东方向端点之间的距离
        double distanceEastToOtherEast = (other.eastLongitude - eastLongitude) % 360;
        double distanceOtherWestToWest = (westLongitude - other.westLongitude) % 360;

        // 修复了较低版本的java，因为余数运算符（%）在一个版本中发生了更改，idk是哪个版本
        if (distanceEastToOtherEast < 0) {
            distanceEastToOtherEast += 360;
        }
        if (distanceOtherWestToWest < 0) {
            distanceOtherWestToWest += 360;
        }

        // 最小距离需要延长
        if (distanceEastToOtherEast <= distanceOtherWestToWest) {
            eastLongitude = other.eastLongitude;
        } else {
            westLongitude = other.westLongitude;
        }

        intersects180Meridian = eastLongitude < westLongitude;
    }

    public double getEastLongitude() {
        return eastLongitude;
    }

    public double getWestLongitude() {
        return westLongitude;
    }

    public double getNorthLatitude() {
        return northLatitude;
    }

    public double getSouthLatitude() {
        return southLatitude;
    }

    public boolean intersects180Meridian() {
        return intersects180Meridian;
    }

    private boolean containsLatitude(double latitude) {
        return latitude >= southLatitude && latitude <= northLatitude;
    }

    private boolean containsLongitude(double longitude) {
        if (intersects180Meridian) {
            return longitude <= eastLongitude || longitude >= westLongitude;
        } else {
            return longitude >= westLongitude && longitude <= eastLongitude;
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj instanceof BoundingBox) {
            BoundingBox that = (BoundingBox) obj;
            return southLatitude == that.southLatitude && westLongitude == that.westLongitude && northLatitude == that.northLatitude && eastLongitude == that.eastLongitude;
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 37 * result + hashCode(southLatitude);
        result = 37 * result + hashCode(northLatitude);
        result = 37 * result + hashCode(westLongitude);
        result = 37 * result + hashCode(eastLongitude);
        return result;
    }

    private static int hashCode(double x) {
        long f = Double.doubleToLongBits(x);
        return (int) (f ^ (f >>> 32));
    }

    @Override
    public String toString() {
        return getNorthWestCorner() + " -> " + getSouthEastCorner();
    }
}
